Controllers and callbacks topic

Controllers & Callbacks

This is part of the kalender documentation.

Controllers drive the calendar from your code. Callbacks report back what the user did. Together they are how the calendar connects to the rest of your app.


Controllers

EventsController

EventsController manages and exposes events to the calendar. Typically one instance per app. Use DefaultEventsController unless you need a custom storage layer.

Method Description
addEvent(event) Add a single event, returns its String id
addEvents(events) Add multiple events, returns List<String> of ids
removeEvent(event) Remove a specific event
removeEvents(events) Remove a list of events
removeWhere(test) Remove events matching a predicate
removeById(id) Remove the event with the given String id
updateEvent({event, updatedEvent}) Replace an existing event (named parameters)
replaceEvents(events) Replace every stored event with the given list, returns List<String> of ids
byId(id) Return the event with the given String id, or null
clearEvents() Remove all events
eventsInRange(range) Events occurring during the given range (requires the view's multiDayRule, plus optional includeMultiDayEvents, includeDayEvents, and location filters)

eventsInRange takes a FloatingDateTimeRange, not a KalenderDateTimeRange. Convert with FloatingDateTimeRange.fromDateTimeRange(range).

KalenderController

KalenderController drives a single KalenderView widget.

State notifiers:

Notifier Type Description
visibleDateTimeRange ValueNotifier<KalenderDateTimeRange?> The currently visible date range
visibleTimeOfDay ValueNotifier<KalenderTime?> Time aligned with the top of the viewport (multi-day views, null otherwise)
visibleEvents ValueNotifier<Set<KalenderEvent>> Events visible on screen
selectedEvent ValueNotifier<KalenderEvent?> The focused event (shows drop target / resize handles)

Navigation methods:

  • jumpToPage(page) / jumpToDate(date)
  • animateToNextPage() / animateToPreviousPage()
  • animateToDate(date) / animateToDateTime(dateTime)
  • animateToEvent(event)

Selection methods: selectEvent(event) focuses an event from code, which is what draws its drop target and resize handles. deselectEvent() clears it. Both drive the selectedEvent notifier above.

Internally the controller delegates to a ViewController (MultiDayViewController, MonthViewController, or ScheduleViewController) depending on the active ViewConfiguration.

Disposing

Both controllers hold listeners, so dispose them with the widget that owns them.

class MyCalendar extends StatefulWidget {
  const MyCalendar({super.key});

  @override
  State<MyCalendar> createState() => _MyCalendarState();
}

class _MyCalendarState extends State<MyCalendar> {
  final eventsController = DefaultEventsController();
  final kalenderController = KalenderController();

  @override
  void dispose() {
    kalenderController.dispose();
    eventsController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) => const SizedBox.shrink();
}

An EventsController shared across screens belongs to whatever owns it for the life of the app, and is disposed there rather than in a single screen.

Building the surrounding UI

The calendar draws no toolbar of its own. Switching views, moving between pages and showing the current month are all built in your app, using the navigation methods above and a ViewConfiguration held in state.

class CalendarScreen extends StatefulWidget {
  const CalendarScreen({super.key});

  @override
  State<CalendarScreen> createState() => _CalendarScreenState();
}

class _CalendarScreenState extends State<CalendarScreen> {
  final eventsController = DefaultEventsController();
  final kalenderController = KalenderController();

  @override
  void dispose() {
    kalenderController.dispose();
    eventsController.dispose();
    super.dispose();
  }

  late final viewConfigurations = <ViewConfiguration>[
    MultiDayViewConfiguration.week(),
    MultiDayViewConfiguration.singleDay(),
    MonthViewConfiguration.singleMonth(),
  ];
  late ViewConfiguration viewConfiguration = viewConfigurations.first;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Row(
          children: [
            // The visible range drives the label, so it updates on every scroll,
            // page change and view switch.
            ValueListenableBuilder(
              valueListenable: kalenderController.visibleDateTimeRange,
              builder: (context, range, child) {
                if (range == null) return const SizedBox.shrink();
                return Text('${range.start.monthNameLocalized()} ${range.start.year}');
              },
            ),
            IconButton(
              onPressed: kalenderController.animateToPreviousPage,
              icon: const Icon(Icons.chevron_left),
            ),
            IconButton(
              onPressed: kalenderController.animateToNextPage,
              icon: const Icon(Icons.chevron_right),
            ),
            IconButton(
              onPressed: () => kalenderController.animateToDate(DateTime.now()),
              icon: const Icon(Icons.today),
            ),
            const Spacer(),
            DropdownButton<ViewConfiguration>(
              value: viewConfiguration,
              items: [
                for (final configuration in viewConfigurations)
                  DropdownMenuItem(value: configuration, child: Text(configuration.name)),
              ],
              onChanged: (value) {
                if (value == null) return;
                setState(() => viewConfiguration = value);
              },
            ),
          ],
        ),
        Expanded(
          child: KalenderView(
            eventsController: eventsController,
            kalenderController: kalenderController,
            viewConfiguration: viewConfiguration,
            header: KalenderHeader(),
            body: KalenderBody(),
          ),
        ),
      ],
    );
  }
}

Switching viewConfiguration is all a view change takes. What carries over, such as the date and scroll position, is set on the configuration itself, see Views.

The basic example has a fuller version of this toolbar.


Callbacks

Pass a KalenderCallbacks to KalenderView to react to user interactions.

KalenderCallbacks(
  // --- Event interactions ---

  // Called when an event tile is tapped.
  onEventTapped: (event) {},

  // Called when an event tile is tapped. Includes tap position detail.
  // The 'detail' parameter provides the tap location, the tile's 'RenderBox' and
  // its exact calculated 'DateTime' position based on the tapped position within
  // the event UI.
  onEventTappedWithDetail: (event, detail) {},

  // Called when an event is secondary tapped (right-clicked).
  onEventSecondaryTapped: (event) {},
  onEventSecondaryTappedWithDetail: (event, detail) {},

  // Called before the calendar creates a new event from a gesture.
  // Return your concrete Event subclass here.
  onEventCreate: (event) {
    return Event(start: event.start,
      end: event.end, title: 'New Event');
  },

  // Same as onEventCreate but includes gesture detail (position, renderBox).
  onEventCreateWithDetail: (event, detail) {
    return Event(start: event.start,
      end: event.end, title: 'New Event');
  },

  // Called after a new event has been committed. Add it to your controller here.
  onEventCreated: (event) => eventsController.addEvent(event),

  // Called just before a rescheduled / resized event is applied.
  onEventChange: (event) {},

  // Called after a rescheduled / resized event is applied.
  onEventChanged: (original, updated) {
    eventsController.updateEvent(event: original, updatedEvent: updated);
  },

  // --- Calendar interactions ---

  // Called when the visible page changes.
  onPageChanged: (visibleDateTimeRange) {},

  // Called when the vertical scroll position of a multi-day view changes.
  // 'visibleTimeOfDay' is the time aligned with the top of the viewport.
  onScrollPositionChanged: (visibleTimeOfDay) {},

  // Called when the user taps an empty area (day / week body).
  onTapped: (date) {},
  onTappedWithDetail: (detail) {
    // detail.dateTime or detail.dateTimeRange, plus renderBox & localOffset.
  },

  // Called when the user secondary taps (right-clicks) an empty area.
  onSecondaryTapped: (date) {},
  onSecondaryTappedWithDetail: (detail) {},

  // Called when the user long-presses an empty area.
  onLongPressed: (date) {},
  onLongPressedWithDetail: (detail) {},

  // Called when the user secondary long-presses an empty area.
  onSecondaryLongPressed: (date) {},
  onSecondaryLongPressedWithDetail: (detail) {},

  // --- Drag-and-drop acceptance ---

  // Day / week vertical drag target. Return false to reject the drop.
  onWillAcceptWithDetailsVertical: (details, controller, configuration) => true,

  // Month / header horizontal drag target.
  onWillAcceptWithDetailsHorizontal: (details, controller, configuration) => true,
)

Typedefs

OnEventChange = void Function(KalenderEvent event) Controllers and callbacks
The callback for when an event is about to be changed.
OnEventChanged = void Function(KalenderEvent event, KalenderEvent updatedEvent) Controllers and callbacks
The callback for when an event is changed.
OnEventCreate = KalenderEvent? Function(KalenderEvent event) Controllers and callbacks
The call back for creating a new event.
OnEventCreated = void Function(KalenderEvent event) Controllers and callbacks
The callback for a new event has been created.
OnEventCreateWithDetail = KalenderEvent? Function(KalenderEvent event, TapDetail detail) Controllers and callbacks
The call back for creating a new event with details.
OnEventTapped = void Function(KalenderEvent event) Controllers and callbacks
The callback for when an event is tapped.
OnEventTappedWithDetail = void Function(KalenderEvent event, TapDetail detail) Controllers and callbacks
The callback for when an event is tapped.
OnLongPressed = void Function(DateTime date) Controllers and callbacks
The callback for when a user long presses on an empty space in the calendar.
OnLongPressedWithDetail = void Function(TapDetail detail) Controllers and callbacks
The callback for when a user long presses on an empty space in the calendar with details.
OnPageChanged = void Function(KalenderDateTimeRange dateTimeRange) Controllers and callbacks
The callback for when a calendar page is changed.
OnScrollPositionChanged = void Function(KalenderTime visibleTimeOfDay) Controllers and callbacks
The callback for when the vertical scroll position of a multi-day view changes.
OnTapped = void Function(DateTime date) Controllers and callbacks
The callback for when a user taps on an empty space in the calendar.
OnTappedWithDetail = void Function(TapDetail detail) Controllers and callbacks
The callback for when a user taps on an empty space in the calendar with details.